<html>
	
	<!-- 
			IMPORTANT
			
			To setup the title of the page use find/replace, 
			and replace 'Your Title Here' by the html text of your title
			
			To setup the link which make the page return the the with-frame content
			use find/replace and replace 'YourFileNameHere' by the 
	-->
	
	<head>
		<title>Création d'un système de particules temps réel - Kairos API Documentation</title>
		
		<link rel="stylesheet" href="../../docs/style.css" type="text/css" media="screen" />
		<link rel="stylesheet" href="../../docs/print.css" type="text/css" media="print" />
		
		<script language="javascript" type="text/javascript" src="../../docs/asdoc.js"></script>
		<script language="javascript" type="text/javascript" src="../js/swfobject.js"></script>
		
	</head>
	<body>
		<script type="text/javascript" language="javascript" src="../../../asdoc.js"></script><script type="text/javascript" language="javascript" src="../../../cookies.js"></script><script type="text/javascript" language="javascript">
		<!--
			asdocTitle = 'Création d\'un système de particules temps réel - Kairos API Documentation';
			var baseRef = '../../docs/';
			window.onload = configPage;
		--></script>
		
		<table id="titleTable" cellspacing="0" cellpadding="0" class="titleTable">
			<tr>
			
				<td align="left" class="titleTableTitle">Kairos API Documentation</td>
				<td align="right" class="titleTableTopNav">
					<a 	onclick="loadClassListFrame('../../docs/all-classes.html')" 
					   	href="../../docs/package-summary.html">All&nbsp;Packages</a>&nbsp;|&nbsp;
					<a 	onclick="loadClassListFrame('../../docs/all-classes.html')" 
						href="../../docs/class-summary.html">All&nbsp;Classes</a>&nbsp;|&nbsp;
					<a 	onclick="loadClassListFrame('../../docs/index-list.html')" 
						href="../../docs/all-index-A.html">Index</a>&nbsp;|&nbsp;
					<a 	href="../../docs/index.html?howto/particle-tutrial-fr.html&amp;class-list.html" id="framesLink1">Frames</a>
					<a 	onclick="parent.location=document.location" 
						href="" 
						style="display:none;" 
						id="noFramesLink1">No&nbsp;Frames</a>
				</td>
				<td rowspan="3" align="right" class="titleTableLogo">
					<img alt="Adobe Logo" title="Adobe Logo" class="logoImage" src="../../docs/images/logo.jpg">
				</td>
			</tr>
			<tr class="titleTableRow2">
				<td align="left" id="subTitle" class="titleTableSubTitle">
					Création d'un système de particules temps réel
				</td>
				<td class="titleTableSubTitle">
					&nbsp;
				</td>			
			</tr>
			<tr class="titleTableRow3">
				<td colspan="2">&nbsp;</td>
			</tr>
		</table
		
		
		<script type="text/javascript" language="javascript">
		<!--
		if (!isEclipse() || window.name != ECLIPSE_FRAME_NAME) 
		{
			titleBar_setSubTitle("Création d'un système de particules temps réel");
			
			titleBar_setSubNav(false,	false,	   false,
							   false,   false,	   false,
							   false,   false,	   false,
							   false,   false,	   false,
							   false,   false
							   );
		}
		--></script>
<div class="MainContent">
	

<p>Dans ce didacticiel nous verrons comment utiliser Kairos
pour créer un mini-système de particules qui sera animé
en temps réel.</p>

<h2>Sommaire</h2>
<ul>
<li><a href="#objectif">Objectif</a></li>
<li><a href="#prerequisite">Pré-requis</a></li>
<li><a href="#concret">Les mains dans le cambouis</a>
<ul>
<li><a href="#generation">Génération des particules</a></li>

<li><a href="#particles">Les particules</a></li>
<li><a href="#animation">Animations des particules</a></li>
</ul></li>
<li><a href="#monitoring">Création d'un champ de monitoring</a></li>
<li><a href="#adjust">Derniers ajustements</a>
<ul>
<li><a href="#motion">Effets de slow/fast motion</a></li>
</ul></li>
</ul>

<hr>

<a name="objectif"></a></p>
<h2>Objectif</h2>

<p>Dans ce didacticiel nous verrons comment utiliser Kairos pour
créer un mini-système de particules qui sera animé en temps réel.
Vous pouvez voir ci-dessous le résultat auquel ce tutorial aboutira.
Passez la souris au dessus du flash pour déclencher l'animation</p>

<div id="flashresult"></div>
		
<script type="text/javascript">
   var so = new SWFObject("../examples/particles/ParticleRTRunner.swf", "result", "500", "130", "9", "#000000");
   so.write("flashresult");
</script>

<p>Note : Certains éléments de ce didacticiel vont à l'encontre de certaines
bonnes pratiques de développement, ceci a été ainsi fait, uniquement
pour ne pas parasiter la démonstration avec des aspects n'ayant pas
de relation directe avec notre objectif.</p>

<p><a name="prerequisite"></a></p>

<h2>Pré-requis</h2>

<p>Il est nécessaire avant d'aborder ce didacticiel de connaître
un minimum la syntaxe AS3 ainsi que les principe de la display list.
De plus, Kairos étant une extension de <a href="http://code.google.com/p/lowra" 
title="Low level Rewrite of Actionscript">LowRA</a>, une connaissance
des concepts mis en oeuvre dans Pixlib est essentielle (notamment la
partie événementielle de Pixlib).</p>

<p><a name="concret"></a></p>

<h2>Les mains dans le cambouis</h2>

<p>Nous allons donc voir comment réaliser un effet de particules
simple fonctionnant en temps réel grâce aux objets de Kairos,
et aussi comment construire de nouveaux objets fonctionnant
en temps réel.</p>

<p><a name="generation"></a></p>

<h3>Génération des particules</h3>

<p>Pour commencer nous allons créer la classe principale de l'exemple,
celle-ci sera chargée de gérer les particules de leur naissance à leur
mort. Cette classe étend la classe <code>Sprite</code>.</p>

<p>Nos particules seront stockées dans un objet <code>Dictionnary</code>,
de plus nous ajouterons de base un masque sur toute la scène ayant pour
but de mettre en pause le swf lorsque la souris est à l'extérieure de celui-ci.
Ceci est réalisé dans les méthodes <code>start</code> et <code>stop</code>
de l'objet.</p>

<p>La génération des particules quand à elle sera assurée par un objet
<code>IntervalRT</code> du package <code>com.kairos.command</code> qui
appellera la méthode <code>createParticle()</code> de notre objet.</p>

<div class="listing"><pre>
package
{
	import com.kairos.commands.IntervalRT;
	import com.bourre.commands.Delegate;
	
	import flash.display.Sprite;
	import flash.utils.Dictionary;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.ui.Mouse;
	import flash.display.StageScaleMode;
	
	[SWF(width="500",height="130",frameRate="120",backgroundColor="#000000")]
	public class ParticleRTRunner extends Sprite
	{
		private var _oGenerator : IntervalRT;
		private var _dParticles : Dictionary;
		private var _oPauseMask : Sprite;
		
		// Notre masque est disponible dans la bibliothèque du fichier .fla dans le répertoire examples de Kairos
		[Embed(source="../lib/example-lib.swf", symbol="PauseMask")]
		private var PauseMask : Class;
		
		public function ParticleRTRunner ()
		{
			// Le swf sera toujours affiché à sa taille réelle
			stage.scaleMode = StageScaleMode.NO_SCALE;
			
			_dParticles = new Dictionary();
			
			// La méthode createParticle sera appelée toute les 50 millisecondes.
			// L'intervalle est à l'arrêt lors de sa création.
			_oGenerator = new IntervalRT ( new Delegate ( createParticle ), 50 );
			
			// Création de notre masque.
			_oPauseMask = new PauseMask () as Sprite;
			_oPauseMask.alpha = .5;
			addChild( _oPauseMask );
			
			// on enregistre nos méthodes pour recevoir les évènements d'entrée et de sortie de la souris dans le swf
			addEventListener( MouseEvent.MOUSE_OVER, start );
			addEventListener( MouseEvent.MOUSE_OUT, stop );
		}
		
		public function start( e : Event = null ) : void
		{
			// On relance la génération des particules
			_oGenerator.start();
			Mouse.hide();
			_oPauseMask.alpha = 0;
		}
		
		public function stop( e : Event = null ) : void
		{
			// On stop la génération des particules
			_oGenerator.stop();
			Mouse.show();
			_oPauseMask.alpha = .5;
		}
		
		// Appeler par notre intervalle, c'est à ce moment que l'on créer les particules
		public function createParticle () : void
		{}
		
		// Comme nous avons une méthode pour créer les particules, nous en avons une pour les supprimer
		public function removeParticle ( p : ParticleRT ) : void
		{}
	}
}
	</pre>
</div>

<p><a name="particles"></a></p>

<h3>Les particules</h3>

<p>Pour le moment notre interval de création est créer mais
il nous manque nos particules. Celles-ci seront les plus
simples possibles, et devront pouvoir être animées en temps réel.
Pour ce faire elles vont étendre la classe <code>RTObject</code>
de Kairos, laquelle fournie de nombreuses méthodes pour se brancher
sur Kairos.</p>

<p>On va donner de nombreuses responsabilités à cette classe,
dans un vrai projet il ne faudrait pas procéder ainsi, mais
pour l'exemple cela conviendra. Par exemple nous transmettons
à la particule le <code>DisplayObjectContainer</code> dans lequel
elle doit s'attacher, ainsi qu'une fonction à appeler lorsque
elle meurt.</p>

<p>Notre particule sera donc une étoile scintillante, elle
sera constituée d'un <code>Sprite</code> contenant un 
<code>MovieClip</code>. Le <code>MovieClip</code> est animé
à l'aide d'un objet <code>MovieClipRT</code>, les dépacements
de la particule seront réalisés par la particule elle même.</p>

<div class="listing">
	<pre>
package
{
	import com.kairos.engine.RTEvent;
	import com.kairos.engine.RTObject;
	import com.kairos.motion.MovieClipRT;
	
	import flash.display.BlendMode;
	import flash.display.DisplayObjectContainer;
	import flash.display.MovieClip;
	import flash.events.Event;
	
	public class ParticleRT extends RTObject
	{
		// Le parent est le MovieClip à controller
		private var _parent : DisplayObjectContainer;
		private var _mcSprite : MovieClip;
		
		// Notre controlleur d'animation
		private var _oSpriteAnimation : MovieClipRT;
		
		// Propriétés nécessaires au comportement de notre particule.
		private var _x : Number;
		private var _y : Number;
		private var _speedX : Number;
		private var _speedY : Number;
		private var _life : Number;
		private var _maxLife : Number;
		
		// Fonction à appeler  à la mort de la particule
		private var _onDeath : Function;
		
		// On met ces valeurs ici, même si ça n'est pas la meilleure approche.
		private static const GRAVITY : Number = 98;
		private static const FRICTION : Number = .995;
		
		// L'apparence de notre particule, elle aussi disponible dans le fichier fla du répertoire exemple
		[Embed(source="../lib/example-lib.swf", symbol="ParticleAnimation")]
		  private var ParticleAnimation : Class;
		
		// Les paramètres de départ seront transmis à la particule par notre classe principale
		public function ParticleRT ( parent:DisplayObjectContainer, x:Number=0, y:Number=0, speedX:Number=0, speedY:Number=0, maxLife:Number=0, scale:Number=1, onDeath:Function=null )
		{
			// on initialise notre particule
			_x = x;
			_y = y;
			_speedX = speedX;
			_speedY = speedY;
			_life = 0;
			_maxLife = maxLife;
			_onDeath = onDeath;
			
			// on s'occupe de sa représentation
			_parent = parent;
			_mcSprite = new ParticleAnimation() as MovieClip;
			_mcSprite.scaleX = _mcSprite.scaleY = scale;
			_mcSprite.x = x;
			_mcSprite.y = y;
			_mcSprite.blendMode = BlendMode.ADD;
			_mcSprite.rotation = Math.floor( Math.random() * 360 ) ;
			_parent.addChild( _mcSprite );
			
			// Notre particule est animée en boucle à la vitesse de 24 ips
			_oSpriteAnimation = new MovieClipRT ( _mcSprite, 24 );
		}
		
		// C'est dans cette méthode que nous allons créer le comportement de la particule.
		override public function onTick(e:Event=null):void
		{
		}
		
		// On reporte un appel de start sur l'animation composée
		override public function start():void
		{
			super.start();
			_oSpriteAnimation.start();
		}
		
		// On reporte un appel de stop sur l'animation composée       
		override public function stop():void
		{
			super.stop();
			_oSpriteAnimation.stop();
		}
	}
}
	</pre>
</div>

De la même façon il faut les créer et les supprimer dans les méthodes
prévues à cet effet dans la classe <code>ParticuleRTRunner</code>.

<div class="listing">
	<pre>
public function createParticle () : void
{
	var p : ParticleRT = new ParticleRT( 
						 this, // le parent de la particule
						 170, // position en x de départ
						 120, // position en y de départ
						 Math.floor( ( 100 + Math.random() * 150 ) * (1-scale/2) ),   // vitesse en x de départ en px/s
						 Math.floor( ( -100 - Math.random() * 100 ) * (1-scale/2) ),  // vitesse en y de départ en px/s
						 Math.floor( 1000 + Math.random() * 2000 ),  // la durée de vie de la particule, comprise entre 1000 et 3000 ms
						 .3 + Math.random() * .7, // l'échelle de la particule, comprise en 30% et 100% de la taille du swf
						 removeParticle  // La méthode à appeller lors de la mort de la particule
						 );
	
	// On ajoute notre particules dans le dictionnaire
	_dParticles[ p ] = p;
	
	// On démarre l'animation de la particule.
	p.start();
}

public function removeParticle ( p : ParticleRT ) : void
{
	p.stop();
	delete _dParticles[ p ];
}
	</pre>
</div>

<p><a name="animation"></a></p>

<h3>Animation des particules</h3>

<p>Nos particules sont désormais créées, le <code>MovieClip</code>
de la particule est mainstenant animé grâce au <code>MovieClipRT</code>,
il ne reste plus qu'à  créer le comportement physique de la particule
dans la méthode <code>onTick de la classe <code>ParticleRT</code>.</p>

<div class="listing">
	<pre>
      public override function onTick(e:Event=null):void
      {
              // On récupère le pas local en secondes
              var stepInSeconds : Number = getLocalStepInSecond( e );
              
			  // On récupère le pas local en millisecondes
              var step : Number = getLocalStep( e );
              
			   // On incrémente la durée de vie de la particule avec notre pas en millisecondes
              _life += step;
			  
			  // Si la vie de la particule dépasse la durée maximum on appelle la méthode callaback avec la particule en paramètre.
              // et on supprime le MovieClip du parent.
              if( _life > _maxLife )
              {
                      _onDeath.call( null, this );
                      _parent.removeChild( _mcSprite );
					  return;
              }
			  
			  // La particule est toujours en vie, on doit donc calculer son nouvel état
			  
			  // On applique la friction à notre vitesse en X
              _speedX = _speedX * FRICTION;
              
			  // On applique la friction et la gravité à notre vitesse en Y
              // étant donné que toutes les vitesses sont décrites en px/s la valeur de gravité
              // à appliquer est : gravité * pas en secondes.
              _speedY = ( _speedY + ( GRAVITY * stepInSeconds ) ) * FRICTION;
              
			  // De la même manière, pour une phase de calcul le déplacement de la particule
              // est : vitesse * pas en secondes
              _x += _speedX * stepInSeconds;
              _y += _speedY * stepInSeconds;
                            
			  // On répercute les modification sur le MovieClip
              _mcSprite.x = _x;
              _mcSprite.y = _y;
      }	
	</pre>
</div>

<p>Pour le moment nos particules sont crées mais s'animent sans prendre
en compte notre système de pause, on va donc modifier les méthodes
<code>start</code> et <code>stop</code> de l'objet <code>ParticleRTRunner</code>
en conséquence.</p>

<div class="listing">
	<pre>
public function start( e : Event = null ) : void
{
	Mouse.hide();
	_oGenerator.start();
	_oPauseMask.alpha = 0;
	
	for each ( var p : ParticleRT in _dParticles )
	{
		p.start();
	}
}

public function stop( e : Event = null ) : void
{
	Mouse.show();
	_oGenerator.stop();
	_oPauseMask.alpha = .5;
	
	for each ( var p : ParticleRT in _dParticles )
	{
		p.stop();
	}
}
	</pre>	
</div>

<p>Pour ajouter un petit plus dans notre démonstration on va ajouter
une collision entre les particules et les bords de l'écran.
Dans la classe <code>ParticleRT</code> nous ajoutons donc :</p>

<div class="listing">
	<pre>
private function _collide():void
{
	if( _x > 500 )
	{
		_x = 500;
		_speedX *= -.5
	}
	
	if( _y > 130 )
	{
		_y = 130;
		_speedY *= -.5
	}
}
	</pre>
</div>

Que nous appellerons dans la méthode <code>onTick</code> ainsi :

<div class="listing">
	<pre>
override public function onTick(e:Event=null):void
{
	// On récupère le pas local en secondes
	var stepInSeconds : Number = getLocalStepInSecond( e );
	
	// On récupère le pas local en millisecondes
	var step : Number = getLocalStep( e );
	
	// On applique la friction à notre vitesse en X
	_speedX = _speedX * FRICTION;
	
	// On applique la friction et la gravité à notre vitesse en Y
	// étant donné que toutes les vitesses sont décrites en px/s la valeur de gravité
	// à appliquer est : gravité * pas en secondes.
	_speedY = ( _speedY + ( GRAVITY * stepInSeconds ) ) * FRICTION;
	
	// De la même manière, pour une phase de calcul le déplacement de la particule
	// est : vitesse * pas en secondes
	_x += _speedX * stepInSeconds;
	_y += _speedY * stepInSeconds;
	
	// Collision avec les bords de l'écran
	_collide();
	
	// On incrémente la durée de vie de la particule avec notre pas en millisecondes
	_life += step;
	
	// On répercute les modification sur le MovieClip
	_mcSprite.x = _x;
	_mcSprite.y = _y;
	
	// Si la vie de la particule dépasse la durée maximum on appelle la méthode callaback avec la particule en paramètre.
	// et on supprime le MovieClip du parent.
	if( _life > _maxLife )
	{
		_onDeath.call( null, this );
		_parent.removeChild( _mcSprite );
	}
}		
	</pre>	
</div>

<p><a name="monitoring"></a></p>

<h2>Création d'un champ de monitoring</h2>

<p>Lors de la conception de n'importe quel système il est
important d'avoir une bonne vision de ces comportements,
dans notre contexte cela peut-être de connaître à tout
moment le nombre de particules à l'écran ainsi que la
cadence constatée du swf, et ce afin de détecter d'éventuelles
pertes de performance.</p>

<p>Kairos fournit un objet <code>Monitor</code> qui permet d'afficher
sous forme de courbes et de textes n'importe quelle valeure à étudier.
Cela est réalisé à l'aide d'objet représentant des champs de
monitoring implémentant l'interface <code>MonitorField</code>.</p>

<p>Nous allons donc créer un champs custom chargé d'afficher
le nombre de particules actuellement dans notre dictionnaire.</p>

<div class="listing">
	<pre>
package
{
	import com.bourre.structures.Range;
	import com.kairos.engine.RTEvent;
	import com.kairos.utils.monitor.AbstractMonitorField;
	
	import flash.utils.Dictionary;
	
	// En étendant AbstractMonitorField on récupère l'ensemble des comportements de base des champs de monitoring
	// il suffit alors juste d'implémenter l'enregistrement des données à étudier.
	public class ParticleRTField extends AbstractMonitorField
	{
		// Le dictionnaire cible
		private var _oDictionary : Dictionary;
		
		public function ParticleRTField(name : String="Untitled", target:Dictionary=null, unit:String="ms", dr:Range=null, col:Number=0xffffff)
		{
			_oDictionary = target;
			super(name, _count(), unit, dr, col);
		}}
		
		// méthode chargée de compter le nombre d'entrée dans le dictionnaire cible
		private function _count () : Number
		{
			var n : Number = 0;
			for each( var i : * in _oDictionary )
			{
				n++;
			}       
			return n;
		}       
		
		// à chaque frame la méthode registerValue est appelée, on enregistre alors le nombre d'entrée dans le dictionnaire.
		override public function registerValue(e:RTEvent):void
		{
		_nLastValue = _nValue;
		_nValue = _count();
		}
	}
}		
	</pre>
</div>

<p>Une fois notre champs crée, il s'agit alors de créer le
monitor dans notre classe  :</p>

<div class="listing">
	<pre>
private var _oMonitor : Monitor;

public function ParticleRTRunner ()
{
	// Le swf sera toujours affiché à sa taille réelle
	stage.scaleMode = StageScaleMode.NO_SCALE;
	_dParticles = new Dictionary();
	
	// La méthode createParticle sera appelée toute les 50 millisecondes.
	// L'intervalle est à l'arrêt lors de sa création.
	_oGenerator = new IntervalRT ( new Delegate ( createParticle ), 50 );
	
	// Création de notre masque.
	_oPauseMask = new PauseMask () as Sprite;
	_oPauseMask.alpha = .5;
	addChild( _oPauseMask );
	
	// Création du moniteur
	_oMonitor = new Monitor ( 150, 80 );
	
	// Ajout des champs de monitoring
	_oMonitor.addField( MonitorFieldsList.FPS );
	_oMonitor.addField( new ParticleRTField ( 
								"Particles", 
							  	_dParticles, 
							  	"particles",
							  	0xffff9900, 
							  	new Range ( 0, 100 ) ) );
	
	// Ajout du moniteur sur la scène.
	addChild( _oMonitor );     
	
	// on enregistre nos méthodes pour recevoir les évènements d'entrée et de sortie de la souris dans le swf
	addEventListener( MouseEvent.MOUSE_OVER, start );
	addEventListener( MouseEvent.MOUSE_OUT, stop );
}		
	</pre>
</div>

<p>Et comme le moniteur est lui même un objet animé on va aussi
l'intégrer dans notre routine de pause :</p>

<div class="listing">
	<pre>
public function start( e : Event = null ) : void
{
	Mouse.hide();
	_oGenerator.start();
	_oPauseMask.alpha = 0;
	_oMonitor.start();
	
	for each ( var p : ParticleRT in _dParticles )
	{
		p.start();
	}
}
public function stop( e : Event = null ) : void
{
	Mouse.show();
	_oGenerator.stop();
	_oPauseMask.alpha = .5;
	_oMonitor.stop();
	
	for each ( var p : ParticleRT in _dParticles )
	{
		p.stop();
	}
}		
	</pre>
</div>

<p><a name="adjust"></a></p>

<h2>Derniers ajustements</h2>

<p>Pour le moment nos particules sont animées en temps réel,
pour s'en convaincre il suffit de faire varier la cadence
du swf (à la compilation ou au runtime), cependant en cas
de gros freeze du swf les particules vont effectuer un gros
saut dans leur animation, dans de nombreux cas ce genre de
comportement peut-être dommageable (un objet traversera un
mur sans le détecter, etc...).</p>

<p>Pour palier à ce problème on va utiliser les méthodes
du <code>RTBeacon</code> prévues à cet effet. Dans le constructeur 
de la classe <code>ParticleRTRunner</code> on ajoute :</p>

<div class="listing">
	<pre>
RTBeacon.getInstance().setMaxStepSize( 100 );
RTBeacon.getInstance().setSmoothness( 10 );		
	</pre>
</div>

<p>On limite ainsi le pas transmis aux objets à 100 millisecondes,
et on créer un effet de lissage des valeurs en utilisant les
10 dernières valeures mesurées.</p>

<p><a name="motion"></a></p>
<h3>Effets de slow/fast motion</h3>

<p>Étant donné que toutes les particules sont soumises au comportement
du <code>RTBeacon</code> il est très facile de créer un effet de slow
motion en utilisant la méthode <code>setGlobalSpeed</code> du
<code>RTBeacon</code> :</p>

<div class="listing">
	<pre>RTBeacon.getInstance().setGlobalSpeed( .5 ); // on divise le pas transmis aux objets par 2</pre>
</div>

</div>

		
	</body>
</html>